package com.hmk.edu.mvcframework.servlet;


import com.hmk.edu.mvcframework.annotations.HjyAutowired;
import com.hmk.edu.mvcframework.annotations.HjyController;
import com.hmk.edu.mvcframework.annotations.HjyRequestMapping;
import com.hmk.edu.mvcframework.annotations.HjyService;
import com.hmk.edu.mvcframework.pojo.Handler;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.http.HttpServlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @className: HJYDispatcherServlet
 * @Description: TODO
 * @author: Huangjianyun
 * @date: 2022/3/22 22:56
 */
public class HJYDispatcherServlet  extends HttpServlet {

    Properties properties = new Properties();
    private List<String>  classNames = new ArrayList<>(); //获取扫描到的类全限定名称
    //创建一个ioc容器
    private Map<String,Object> ioc = new HashMap<>();
    //缓存 url和 handler处理器方法关联 map容器
    //private Map<String,Method> handlerMapping = new HashMap<>();
    private List<Handler> handlerMapping  = new ArrayList<>();
    @Override
    public void init(ServletConfig config){
        //1加载配置文件， 这里定义springmvc.properties文件
        //在web.xml指定加载配置文件传入，初始化进行加载该springmvc.properties配置文件
        //从web.xml中获取配置文件springmvc.properties路径
        String contextConfigLocation = config.getInitParameter("contextConfigLocation");
        doLoadConfig(contextConfigLocation);
        //2扫描相关类和注解
        doScan(properties.getProperty("scanPackage"));
        //3初始化bean对象（实现ioc容器 这里基于注解实现ioc容器）
        doInstance();
        //4 维护对象之间依赖关系，实现依赖注入
        doAutowired();
        //5 初始化springmvc的相关组件，这里初始化HandlerMapping(处理映射器) 即处理url和handler方法的映射关系
        initHandlerMapping();

        System.out.println("hjy  mvc  初始化完成....");
        //6等待请求进行处理请求
    }


    /*
    *
    *构造一个HandlerMapping 的处理器映射器
    *最关键环节
    * 目的：将url 和 method方法建立关联
    * */
    private void initHandlerMapping() {
        //1判断ioc容器没有bean实例 没有意义 直接返回
        if(ioc.isEmpty()){
            return;
        }
        //2遍历ioc容器所有的bean实例

        for (Map.Entry<String, Object> entry :ioc.entrySet()) {
            //3获取ioc容器中当前遍历的对象的class类型
            Class<?> aClass = entry.getValue().getClass();
            //4判断 service 和 controller层类； 这里url和handler方法映射 只对controller包下类处理
            if(!aClass.isAnnotationPresent(HjyController.class)){
                //如果类上面没有包含@HjyController注解 抱歉不做任何处理
                continue;
            }

            String baseUrl = "";
            //类上面有这个@HjyController注解   在判断是否还有@HjyRequestMapping("/demo")
            if(aClass.isAnnotationPresent(HjyRequestMapping.class)){
                //有@HjyRequestMapping("/demo") 获取该注解里面路径 作为基础路径
                baseUrl = aClass.getAnnotation(HjyRequestMapping.class).value();  //  /demo值

            }
            //否则该类上面包含@HjyController注解
            //获取controller类下面所有的方法  这里是DemoController
            Method[] methods = aClass.getMethods();
            //遍历这些方法  这里只写了一个是query（）方法
            for (int i = 0; i < methods.length; i++) {
                Method method = methods[i];
                //在进行判断 方法上面是否@HjyRequestMapping注解
                //如果方法没有该注解就不做任何处理
                if(!method.isAnnotationPresent(HjyRequestMapping.class)){
                    continue;
                }
                //该方法上有这个注解 @HjyRequestMapping("/query") 获取到这个注解@HjyRequestMapping
                HjyRequestMapping annotation = method.getAnnotation(HjyRequestMapping.class);
                 //在获取注解里面value属性值
                String methodUrl = annotation.value(); // /query
                String url  = baseUrl + methodUrl;   //   /demo/query 计算出来url

                //把method相关信息及url封装成 一个handler实体
                Handler handler = new Handler(entry.getValue(),method, Pattern.compile(url));

                //处理计算方法参数位置信息 public  String query(HttpServletRequest request, HttpServletResponse response,String name)
                Parameter[] parameters = method.getParameters();
                //这里只做几种参数类型处理 遍历方法参数
                for (int j = 0; j < parameters.length; j++) {
                    Parameter parameter = parameters[j];
                    if(parameter.getType() == HttpServletRequest.class ||  parameter.getType() == HttpServletResponse.class){
                        //如果是request对象或者响应对象  HttpServletRequest HttpServletResponse
                        //参数类型作为参数名称（key）   i作为参数索引位置
                        handler.getParamIndexMapping().put(parameter.getType().getSimpleName(),j);
                    }else{
                        //如果是普通类型  <name,2>
                        handler.getParamIndexMapping().put(parameter.getName(),j);
                    }
                }
                //建立url和method方法之间的映射关系 并进行获取起来
                handlerMapping.add(handler);
            }
        }

    }

    //实现依赖注入
    private void doAutowired() {
        //判断 属性上面有没有@HjyAutowire注解， 注解指向是哪一个service接口实现类
        if(ioc.isEmpty()){
            return;
        }
        //ioc容器 bean对象 进行依赖维护
        //遍历ioc容器所有的bean 查看bean对象中字段， 看字段上面是否有@HjyAutowire注解，如果有的话进行维护依赖注入的关系
        for (Map.Entry<String, Object> entry :ioc.entrySet()) {
            //获取bean的字段信息  entry.getValue().getClass 获取某个bean的类型， getDeclaredFields()获取类中所有声明字段
            Field[] declaredFields = entry.getValue().getClass().getDeclaredFields();
            //遍历获取到类中所有声明字段
            for (int i = 0; i < declaredFields.length; i++) {
                //遍历到某个字段
                Field declaredField = declaredFields[i];
                // 遍历到@HjyAutowired  private IDemoService iDemoService; 这个字段且上面有@HjyAutowired注解时
                if(!declaredField.isAnnotationPresent(HjyAutowired.class)){
                    //没有该注解
                    continue;
                }
                //获取字段有该注解@HjyAutowired注解
                HjyAutowired annotation = declaredField.getAnnotation(HjyAutowired.class);
                //判断该注解 annotation里面value属性是否自定义 没有使用注入接口的名称作为bean的名称
                //注解有设置value值
                String beanName = annotation.value();
                if("".equals(beanName.trim())){
                    //注解没有设置value属性值
                    //没有配置bean id  那么把当前字段名称的作为bean id 进行接口注入， IDemoService
                    beanName = declaredField.getType().getName();
                }
                //开启赋值  设置强制访问
                declaredField.setAccessible(true);
                try {
                    //字段进行赋值 给那个类下 entry.getValue类（IDemoController）下字段  赋什么值（ioc.get(beanName)）
                    declaredField.set(entry.getValue(),ioc.get(beanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //ioc容器
    //基于classNames缓存类的全限定名称， 以及反射技术，完成对象的创建和管理
    private void doInstance()  {
        //容器没有存储到扫描需要的类全限定路径 不做任何处理直接返回
        if(classNames.size() == 0) {
            return;
        }

        try {
            //否则遍历 存储的类路径容器
            for (int i = 0; i < classNames.size(); i++) {
                //获取类全限定名称 如：com.hmk.controller.DemoController这个类权限定路径
                String className = classNames.get(i);
                //根据路径进行反射 获取类型
                Class<?> aClass = Class.forName(className);
                //为了区分controller, 区分service 判断加载类上面是什么注解
                //如果加载类上面注解是 HjyController这个注解 说明controller类
                if(aClass.isAnnotationPresent(HjyController.class)){
                    //1controller 的id此处不做任何处理， 不取value, 就拿类的首字母小写字母作为id 保存到ioc容器
                    String simpleName = aClass.getSimpleName();  //获取controller下某个类的名称： DemoController
                    String lowerFirstSimpleName = lowerFirst(simpleName); // demoController
                    //2实例化 存储在ioc容器进行管理
                    Object o = aClass.newInstance();
                    ioc.put(lowerFirstSimpleName,o);
                }else if(aClass.isAnnotationPresent(HjyService.class)){
                    //如果service包下 service类 如IDemoServiceImpl
                    //1获取service类上面的注解
                    HjyService annotation = aClass.getAnnotation(HjyService.class);
                    //2获取注解里面的value属性值 如果有的话 注解value属性值作为实例化bean的名称 添加ioc容器
                    String beanName = annotation.value();
                    //3进行判断
                    if(!"".equalsIgnoreCase(beanName)){
                        ioc.put(beanName,aClass.newInstance());
                    }else{
                        //注解value属性为null 没有的话就设置类的首字母改成小写作为bean实例化名称 添加ioc容器
                        ioc.put(lowerFirst(aClass.getSimpleName()),aClass.newInstance());
                    }
                    //service层往往有接口， 面向接口开发， 以接口名为id, 放入一份到ioc容器中，便于后期 根据接口类型注入
                    //获取service实现全部接口
                    Class<?>[] interfaces = aClass.getInterfaces();
                    //遍历 接口名做beanName  接口视线里实例化并添加ioc容器
                    for (int j = 0; j < interfaces.length; j++) {
                        Class<?> anInterface = interfaces[j];
                        ioc.put(anInterface.getName(),aClass.newInstance()); //aClass指的是service实现类
                    }
                }else{
                    continue;
                }

            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    //首字母的大写字母进行转换小写字母
    public String lowerFirst(String str){
        char[] chars = str.toCharArray();
        if('A'<=chars[0] && chars[0]<='Z'){
            //将首字母大写转换为小写字母
            chars[0]+=32;
        }
        return String.valueOf(chars);
    }

    //扫描类
    //获取类全路径

    //scanPackage: com.hjy.demo  扫描包 涉及递归
    private void doScan(String scanPackage) {
        //获取包 在磁盘绝对路径
       String scanPackagePath  =  Thread.currentThread().getContextClassLoader().getResource("").getPath()+scanPackage.replaceAll("\\.","/");
        //根据包路径 创建File对象 借助File对象判断包下子文件或者子目录
        File pack = new File(scanPackagePath);
        File[] files = pack.listFiles();
        for (File file:files) {
            if(file.isDirectory()){ //子包
                //递归找到类  路径为子包的全路径： com.hjy.demo+ "."+子包名称
                doScan(scanPackage+"."+file.getName());
            }else if(file.getName().endsWith(".class")){ //如果子文件是.class结尾，说明这个文件是类
                //获取类的全路径 把类 “.class” 使用 “”替代  com.hjy.demo+"."+类名称(没有.class)
                String className = scanPackage+"."+file.getName().replaceAll(".class","");
                //类路径获取到list容器中
                classNames.add(className);
            }
        }
    }

    //加载配置文件
    private void doLoadConfig(String contextConfigLocation) {
        //文件读取 输入流
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        //properties对象调用load方法加载配置文件 读取到内存中
        try {
            properties.load(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //处理请求
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req,resp);
    }

    //处理请求
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       //根据url 找到对应的method方法， 进行调用
        //获取uri
/*        String requestURI = req.getRequestURI();
        //从请求路径和method相关联的map容器 根据路径找打 method方法
        Method method = handlerMapping.get(requestURI);
        //但是进行反射方法调用  map容器没有进行缓存 对象  请求参数 导致方法无法完成调用
        method.invoke();*/
        //根据uri获取能够处理当前请求的handler（从handlermapping（list））
        Handler handler = getHandler(req);
        if(handler == null){
            resp.getWriter().write("404 not Found");
            return;
        }
        //匹配到路径 进行handler方法调用 处理请求
        //参数绑定
        //获取所有参数类型数组， 这个数组长度就是 args传入多个参数的长度
        Class<?>[] parameterTypes = handler.getMethod().getParameterTypes();
        // 根据上述数据长度 创建一个新的数组（参数数组，是要被反射调用）
        Object[] paraValues = new Object[parameterTypes.length];
        //以下就是为了参数数组塞值，而且还要保证参数顺序和方法参数顺序一致 value使用String[]是因为同一个请求参数可能多个值
        Map<String,String[]> parameterMap = req.getParameterMap();
        //遍历request中所有参数
        for (Map.Entry<String,String[]> param: parameterMap.entrySet()) {
            // name=1&name=2         name  [1,2]
            String value = StringUtils.join(param.getValue(),",");
            //如果请求参数和方法中参数匹配上 填充数据
            if(!handler.getParamIndexMapping().containsKey(param.getKey())){
                //不匹配
                continue;
            }
            //匹配上  方法形参中确实有这个参数， 找到索引位置， 对应把参数值放入paraValues数组中
            Integer index = handler.getParamIndexMapping().get(param.getKey()); //获取方法参数索引位置 name在第二个位置
            paraValues[index] = value;

        }
        int requestIndex = handler.getParamIndexMapping().get(HttpServletRequest.class.getSimpleName()); //0
        paraValues[requestIndex] = req;
        int responseIndex = handler.getParamIndexMapping().get(HttpServletResponse.class.getSimpleName());//1
        //赋值
        paraValues[responseIndex] = resp;

        //最终调用handler的method的属性
        try {
            handler.getMethod().invoke(handler.getController(),paraValues);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

    }
    private Handler getHandler(HttpServletRequest req) {
        if(handlerMapping.isEmpty()){return  null;}

        //获取请求资源路径
        String url = req.getRequestURI();
        //遍历 handlerMapping映射关系list容器（存储handler， 封装 方法所在对象  方法 正则（url匹配） 参数索引map容器）
        for (Handler handler : handlerMapping) {
            Matcher matcher = handler.getPattern().matcher(url); //使用正则是否匹配url路径
            //如果不匹配  退出当前循环 跳出循环后 return null 返回null值
            if(!matcher.matches()){
                continue;
            }
            //如果匹配 直接返回handler实体类
            return handler;
        }
        return null; //不匹配
    }
}
